package com.market.web.controller;

import java.util.List;
import java.util.Map;

import javax.faces.event.ActionEvent;

import org.primefaces.model.StreamedContent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;

import com.market.domain.Location;
import com.market.domain.Order;
import com.market.domain.Order.STATUS;
import com.market.domain.OrderDetail;
import com.market.domain.Product;
import com.market.domain.Promotion;
import com.market.domain.User;
import com.market.domain.Weekly;
import com.market.domain.transients.MarketDate;
import com.market.domain.transients.ReportHeaderFooter;
import com.market.exception.OutOfStockException;
import com.market.manager.OrderManager;
import com.market.util.CollectionUtil;
import com.market.web.model.OrderModel;
import com.market.web.model.ProductModel;
import com.market.web.model.ShoppingCartModel;
import com.market.web.model.UserModel;
import com.market.web.model.report.ReportModel;

@Controller("orderController")
@Scope("session")
public class OrderController extends
		GenericController<OrderManager, OrderModel, Order> {

	@Autowired
	private ProductModel productModel;
	@Autowired
	private UserModel userModel;
	@Autowired
	private EmailController emailController;
	@Autowired
	private ReportModel reportModel;
	@Autowired
	private ShoppingCartModel shoppingCart;
	@Autowired
	private WeeklyController weeklyController;
	@Autowired
	private PromotionController promotionController;

	@Autowired
	@Override
	protected void setManager(OrderManager manager) {
		this.manager = manager;
	}

	@Autowired
	@Override
	protected void setModel(OrderModel model) {
		this.model = model;
	}

	public void loadByQuery() {
		List<Order> orders = this.manager.fetchOrders(this.model
				.getOrderQuery());
		this.model.setList(orders);
		if (orders.size() > 0) {
			super.refreshCurrent();
		}

	}
	
	/**
	 * Send an email to all customer in list 
	 */
	public void sendEmailToCustomer(){
		for(Order order : this.model.getList()){
			this.model.getEmail().setTo(order.getCustomer().getEmail());
			emailController.sendEmail(this.model.getEmail());
		}
	}

	/**
	 * Customer use this method to edit their orders, the order can only be
	 * edited before confirm.
	 */
	public String editCurrent(ActionEvent event) {

		Map<String, Object> attrs = event.getComponent().getAttributes();
		Order order = (Order) attrs.get("order");
		order = this.fetchOne(order);
		this.model.setCurrent(order);
		User customer = order.getCustomer();
		Location loc = customer.getDropOffLocation();
		List<Weekly> activeWeeklys = weeklyController.fetchActiveWeekly();
		shoppingCart.getActiveWeeklys().clear();
		for (Weekly weekly : activeWeeklys) {
			weekly = this.weeklyController.fetchOne(weekly);
			shoppingCart.getActiveWeeklys().add(weekly);
		}
		List<Promotion> promotions = promotionController.fetchValidPromotion(
				order.getOrderedDate(), loc.getId());
		shoppingCart.setCustomer(customer);
		shoppingCart.setLocation(loc);
		shoppingCart.setPromotions(promotions);
		shoppingCart.setOrder(order);
		shoppingCart.setRequiredDate(new MarketDate(order.getRequiredDate()));

		String outCome = "EDIT ORDER";
		super.getNavHandler().performNavigation(outCome);
		return outCome;
	}

	/**
	 * This is different from save(Order order) method, because this method have
	 * to consider minus user's account credit;
	 * 
	 * @param order
	 * @return
	 */
	public Order userSave(Order order) {
		order = this.manager.userStore(order);
		order = manager.fetchOne(order);
		this.model.setCurrent(order);
		if (order.getCustomer().getIsEmailOrderStatus()) {
			emailController.sendEmail(order.getEmail());
		}
		return order;
	}

	public Order save(Order order) {
		order = this.manager.store(order);
		order = manager.fetchOne(order);
		this.model.setCurrent(order);
		if (order.getCustomer().getIsEmailOrderStatus()) {
			emailController.sendEmail(order.getEmail());
		}
		return order;
	}

	public void delete(Order order) {
		this.manager.delete(order);
	}

	public void deleteCurrent() {
		Order current = this.getModelCurrent();
		if (current.getStatus() == STATUS.CONFIRMED) {
			this.unConfirmCurrent();
		}
		this.delete(current);
		this.model.remove(current);
	}

	public Order confirm(Order order) {
		order = manager.confirm(order);
		order = manager.fetchOne(order);
		this.model.setCurrent(order);
		if (order.getCustomer().getIsEmailOrderStatus()
				&& order.getCustomer().getIsEmailStatusConfirm()) {
			emailController.sendEmail(order.getEmail());
		}
		return order;
	}

	/**
	 * This method is different from confirm(Order order) method, because have
	 * to consider minus user's account credit;
	 * 
	 * @param order
	 * @return
	 */
	public Order userConfirm(Order order) {
		order = manager.userConfirm(order);
		order = manager.fetchOne(order);
		this.model.setCurrent(order);
		if (order.getCustomer().getIsEmailOrderStatus()) {
			emailController.sendEmail(order.getEmail());
		}
		return order;
	}

	public Order confirmCurrent() {
		try {
			Order order = this.confirm(this.getModelCurrent());
			return order;
		} catch (OutOfStockException e) {
			this.popMessage("Out of Stock", e.getNumber() + ":" + e.getName()
					+ ",Inventory is " + e.getInventory());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	/**
	 * This method automatically confirm current order, include two step: 1.set ordered quantity and  price to confirmed 
	 * quantity and price 2. confirm order.
	 * @return
	 */
	public Order autoConfirmCurrent() {
		try {
			this.getModelCurrent().orderedToConfirmed();
			return this.confirmCurrent();
		} catch (OutOfStockException e) {
			this.popMessage("Out of Stock", e.getNumber() + ":" + e.getName()
					+ ",Inventory is " + e.getInventory());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public Order unConfirmCurrent() {
		return this.manager.unConfirm(this.getModelCurrent());
	}
	
	/*
	 * This method automatically prepare current order, include two step: 1. copy confirmedQuantity and confirmedPrice to
	 * deliveredQuantity and deliveredPrice; 2. Prepare current order.
	 */
	public Order autoPrepareCurrent(){
		this.getModelCurrent().confirmedToDelivered();
		return this.prepareCurrent();
	}

	public Order prepareCurrent() {
		Order order = this.manager.prepare(getModelCurrent());
		if (order.getCustomer().getIsEmailOrderStatus()
				&& order.getCustomer().getIsEmailStatusPrepare()) {
			this.emailController.sendEmail(order.getEmail());
		}
		return order;
	}

	public Order unPrepareCurrent() {
		return this.manager.unPrepare(getModelCurrent());
	}

	public Order deliverCurrent() {
		Order order = this.manager.deliver(getModelCurrent());
		if (order.getCustomer().getIsEmailOrderStatus()
				&& order.getCustomer().getIsEmailStatusDeliver()) {
			emailController.sendEmail(order.getEmail());
		}
		return order;
	}

	public Order unDeliverCurrent() {
		return this.manager.unDeliver(getModelCurrent());
	}

	public Order pickupCurrent() {
		return this.manager.pickup(getModelCurrent());
	}

	public Order unPickupCurrent() {
		return this.manager.unPickup(getModelCurrent());
	}
	
	/**
	 * Do some preparation before pay, eg. copy  delivered total charge to paid amount.
	 */
	
	public void prePayCurrent(){
		Order current = this.getModelCurrent();
		current.setPaidAmount(current.getDeliveredTotalCharge());
	}

	public Order payCurrent() {
		Order order = this.manager.pay(getModelCurrent());
		if (order.getCustomer().getIsEmailOrderStatus()
				&& order.getCustomer().getIsEmailStatusPay()) {
			this.emailController.sendEmail(order.getEmail());
		}
		return order;
	}

	public Order unPayCurrent() {
		return this.manager.unPay(getModelCurrent());
	}

	public Order cancelCurrent() {
		return this.manager.cancel(getModelCurrent());
	}

	public Order restoreCurrent() {
		return this.manager.restore(getModelCurrent());
	}

	/**
	 * This method does nothing, just for setRemoveDetailFromCurrent work
	 * properly.
	 * 
	 * @param detail
	 */

	public void removeDetailFromCurrent() {

	}

	public StreamedContent getOrderListPdf() {
		ReportHeaderFooter hFooter = new ReportHeaderFooter();
		hFooter.setCenterHeader("Order List");
		if (this.getModel().getOrderQuery().getLocation() != null) {
			hFooter.setLeftHeader("Location:"
					+ this.getModel().getOrderQuery().getLocation().getNumber()
					+ " "
					+ this.getModel().getOrderQuery().getLocation()
							.getAddress());
		}
		if (this.getModel().getOrderQuery().getRequiredDate() != null) {
			hFooter.setRightHeader("Required Date:"
					+ this.getModel().getOrderQuery().getRequiredDateLabel());
		}

		this.reportModel.setReportHeaderFooter(hFooter);
		for (int i = 0; i < this.getModel().getList().size(); i++) {
			this.getModel().getList()
					.set(i, this.fetchOne(this.getModel().getList().get(i)));
		}
		this.reportModel.setOrderList(this.getModel().getList());
		return this.reportModel.getOrderListPdf();
	}

	public StreamedContent getOrderDetailListPdf() {
		ReportHeaderFooter hFooter = new ReportHeaderFooter();
		hFooter.setCenterHeader("Order Detail List");
		if (this.getModel().getOrderQuery().getLocation() != null) {
			hFooter.setLeftHeader("Location:"
					+ this.getModel().getOrderQuery().getLocation().getNumber()
					+ " "
					+ this.getModel().getOrderQuery().getLocation()
							.getAddress());
		}
		if (this.getModel().getOrderQuery().getRequiredDate() != null) {
			hFooter.setRightHeader("Required Date:"
					+ this.getModel().getOrderQuery().getRequiredDateLabel());
		}

		this.reportModel.setReportHeaderFooter(hFooter);
		for (int i = 0; i < this.getModel().getList().size(); i++) {
			this.getModel().getList()
					.set(i, this.fetchOne(this.getModel().getList().get(i)));
		}
		this.reportModel.setOrderList(this.getModel().getList());
		return this.reportModel.getOrderDetailListPdf();
	}

	/**
	 * This method remove specific order detail from order.
	 * 
	 * @param detail
	 */
	public void setRemoveDetailFromCurrent(OrderDetail detail) {
		for (int i = 0; i < this.getModelCurrent().getOrderDetails().size(); i++) {
			if (this.getModelCurrent().getOrderDetails().get(i).getProduct()
					.equalsId(detail.getProduct())) {
				this.getModelCurrent().getOrderDetails().remove(i);
			}
		}
	}

	public void addSelectedProductToCurrent() {
		Order current = this.getModelCurrent();
		List<Product> selectedProducts = productModel.getSelecteds();

		List<OrderDetail> orderDetails = current.getOrderDetails();
		boolean exist = false;
		for (Product product : selectedProducts) {
			exist = false;
			for (OrderDetail orderDetail : orderDetails) {
				if (product.equalsId(orderDetail.getProduct())) {
					exist = true;
					break;
				}
			}
			if (exist == false) {
				current.getOrderDetails().add(
						this.model.createOrderDetail(product));
			}
		}
		CollectionUtil<Product> util = new CollectionUtil<Product>();
		util.clearSelection(selectedProducts);
	}

	public void removeSelectedOrderDetailFromCurrent() {
		List<OrderDetail> orderDetails = this.getModel().getCurrent()
				.getOrderDetails();
		CollectionUtil<OrderDetail> util = new CollectionUtil<OrderDetail>();
		List<OrderDetail> unSelectedDetails = util
				.extractUnSelected(orderDetails);
		this.getModelCurrent().setOrderDetails(unSelectedDetails);
	}

	/**
	 * This method use for showing customers' orders.
	 */
	public void fetchOrdersFromUser() {
		User user = this.userModel.getAuthenticatedUser();
		this.model.setList(user.getOrders());
	}

	public void updateCurrentCustomer() {
		this.model.getCurrent().setCustomer(this.userModel.getCurrent());
	}
}
